package core;
import stat.VoidStat;

import static com.googlecode.javacv.cpp.opencv_core.CvSeq;
import static com.googlecode.javacv.cpp.opencv_core.CvSize;
import static com.googlecode.javacv.cpp.opencv_core.CvPoint;
import static com.googlecode.javacv.cpp.opencv_core.IplImage;
import static com.googlecode.javacv.cpp.opencv_core.cvClearMemStorage;
import static com.googlecode.javacv.cpp.opencv_imgproc.CV_BGR2GRAY;
import static com.googlecode.javacv.cpp.opencv_imgproc.CV_GAUSSIAN;

//import static com.googlecode.javacv.cpp.opencv_imgproc.cvCanny;
import static com.googlecode.javacv.cpp.opencv_imgproc.cvCvtColor;
import static com.googlecode.javacv.cpp.opencv_imgproc.cvSmooth;

import static com.googlecode.javacv.cpp.opencv_imgproc.CV_THRESH_BINARY;
//import static com.googlecode.javacv.cpp.opencv_imgproc.CV_THRESH_OTSU;
//import static com.googlecode.javacv.cpp.opencv_imgproc.CV_THRESH_TOZERO;
//import static com.googlecode.javacv.cpp.opencv_imgproc.CV_RETR_LIST;
import static com.googlecode.javacv.cpp.opencv_imgproc.CV_RETR_CCOMP;
import static com.googlecode.javacv.cpp.opencv_imgproc.CV_CHAIN_APPROX_SIMPLE;
import static com.googlecode.javacv.cpp.opencv_imgproc.CV_ADAPTIVE_THRESH_GAUSSIAN_C;
import static com.googlecode.javacv.cpp.opencv_imgproc.CV_ADAPTIVE_THRESH_MEAN_C;
import static com.googlecode.javacv.cpp.opencv_core.CV_WHOLE_SEQ;

import static com.googlecode.javacv.cpp.opencv_core.CvMemStorage;
//import static com.googlecode.javacv.cpp.opencv_imgproc.cvThreshold;
import static com.googlecode.javacv.cpp.opencv_imgproc.cvAdaptiveThreshold;
import static com.googlecode.javacv.cpp.opencv_imgproc.cvFindContours;
import static com.googlecode.javacv.cpp.opencv_imgproc.cvContourArea;
import static com.googlecode.javacv.cpp.opencv_imgproc.cvArcLength;

//import static com.googlecode.javacv.cpp.opencv_imgproc.cvErode;
//import static com.googlecode.javacv.cpp.opencv_imgproc.cvDilate;
//import static com.googlecode.javacv.cpp.opencv_imgproc.cvCreateStructuringElementEx;
//import static com.googlecode.javacv.cpp.opencv_imgproc.CV_SHAPE_RECT;
//import static com.googlecode.javacv.cpp.opencv_imgproc.CV_SHAPE_ELLIPSE;

import static com.googlecode.javacv.cpp.opencv_core.cvGetSeqElem;
import static com.googlecode.javacv.cpp.opencv_core.cvOr;
//import static com.googlecode.javacv.cpp.opencv_core.cvNot;
//import static com.googlecode.javacv.cpp.opencv_core.cvXor;
//import static com.googlecode.javacv.cpp.opencv_core.cvAnd;

//import static com.googlecode.javacv.cpp.opencv_core.cvDrawCircle;
//import static com.googlecode.javacv.cpp.opencv_core.CV_FILLED;
import static com.googlecode.javacv.cpp.opencv_core.CV_RGB;
//import static com.googlecode.javacv.cpp.opencv_core.IPL_DEPTH_8U;
import static com.googlecode.javacv.cpp.opencv_core.cvCreateImage;
import static com.googlecode.javacv.cpp.opencv_core.cvCopy;
import static com.googlecode.javacv.cpp.opencv_core.cvDrawContours;
import static com.googlecode.javacv.cpp.opencv_core.cvGetSize;
//import static com.googlecode.javacv.cpp.opencv_core.cvLine;
import static com.googlecode.javacv.cpp.opencv_core.cvPoint;
import static com.googlecode.javacv.cpp.opencv_core.cvRect;
import static com.googlecode.javacv.cpp.opencv_core.cvSetImageROI;
import static com.googlecode.javacv.cpp.opencv_core.cvResetImageROI;
//import static com.googlecode.javacv.cpp.opencv_core.cvSize;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Random;

import com.googlecode.javacpp.Loader;
import com.googlecode.javacv.CanvasFrame;
import com.googlecode.javacv.cpp.opencv_core.CvContour;
//import com.googlecode.javacv.cpp.opencv_imgproc.IplConvKernel;

import com.googlecode.javacv.cpp.opencv_core.CvScalar;

/**
 * 
 * @author Hyunchang, Sangyoun, Yunseong Ball Detector from X-ray image of BGA
 *         (Ball-Grid Array)
 * 
 */

public class VoidDetector {
	private IplImage original;
	private IplImage result;
	private ArrayList<CvSeq> circlesAL;
	private ArrayList<VoidStat> voidStat;
	private ArrayList<CvSeq> voidsAL;

	private IplImage[] croppedImages;
	int[][] pointsNum;

	// Construct with the image to process and the circles.
	// TODO : erase "CvSeq circles"
	public VoidDetector(IplImage original, ArrayList<CvSeq> circlesAL) {
		this.original = original;
		this.result = original.clone();
		this.circlesAL = circlesAL;
		this.voidsAL = new ArrayList<CvSeq>();
		this.voidStat = new ArrayList<VoidStat>();
	}
	
	public IplImage getOriginal() {
		return original;
	}
	public IplImage getResult() {
		return result;
	}
	public ArrayList<CvSeq> getVoidsAL() {
		return voidsAL;
	}

	public ArrayList<VoidStat> getVoidStat() {
		return voidStat;
	}

	public IplImage divideAndConquer(boolean debug) {
		for (int i = 0; i < croppedImages.length; i++) {
			detectEachBallVoid(i, debug);
		}
		if (debug) {
			CanvasFrame frame = new CanvasFrame("balls");
			frame.showImage(result);
		}
		return result;
	}

	public void detectEachBallVoid(int i, boolean debug) {
		IplImage binary = cvCreateImage(cvGetSize(croppedImages[i]), croppedImages[i].depth(), 1);
		cvCvtColor(croppedImages[i], binary, CV_BGR2GRAY);
		IplImage binary2 = cvCreateImage(cvGetSize(croppedImages[i]), croppedImages[i].depth(), 1);
		cvCvtColor(croppedImages[i], binary2, CV_BGR2GRAY);
		
		cvSmooth(binary, binary, CV_GAUSSIAN, 7, 7, 3.0, 0.0);
		cvSmooth(binary2, binary2, CV_GAUSSIAN, 7, 7, 3.0, 0.0);
		
	    cvAdaptiveThreshold(binary, binary, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C
	    		, CV_THRESH_BINARY, getBlocksize(croppedImages[i],3), -1);
	    cvAdaptiveThreshold(binary2, binary2, 255, CV_ADAPTIVE_THRESH_MEAN_C
	    		, CV_THRESH_BINARY, getBlocksize(croppedImages[i],2), -1);
	    
	    cvOr(binary, binary2, binary, null);

	    /* TODO : erode and dilate
	    IplConvKernel elem = cvCreateStructuringElementEx(3,3,1,1,CV_SHAPE_ELLIPSE, null);
	    
	    cvErode(binary, binary, elem, 1);
	    cvDilate(binary, binary, elem, 1);
	    */
	    
    	CvSeq cvSeq=new CvSeq();
	    CvMemStorage memory=CvMemStorage.create();
	    cvFindContours(binary, memory, cvSeq, Loader.sizeof(CvContour.class), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
	
	    double sumOfAreas = 0.0;
	    int numberOfVoids = 0;
	    
		Random rand = new Random();
		for (CvSeq ptr = cvSeq; ptr!=null; ptr = ptr.h_next()) {		
			Color randomColor = new Color(rand.nextFloat(), rand.nextFloat(),
					rand.nextFloat());
			CvScalar color = CV_RGB(randomColor.getRed(),
					randomColor.getGreen(), randomColor.getBlue());
			
			if (!ptr.isNull()) { 
				double area = cvContourArea(ptr, CV_WHOLE_SEQ,0);
				double arcLength = cvArcLength(ptr, CV_WHOLE_SEQ, 1);
				if(/*area>=20*/
						/*&&*/ !isBackground(ptr, croppedImages[i].width(), croppedImages[i].height())
						&& compactness(area,arcLength) > 0.45)
				{
					numberOfVoids++;
					sumOfAreas += area;
					adjustLocation(ptr, pointsNum[i][2], pointsNum[i][0]);
					voidsAL.add(ptr);
					cvDrawContours(result, ptr, color, CV_RGB(0, 0, 0), -1,
									1, 8, cvPoint(0, 0));
				}
			}
			else break;
		}
		voidStat.add(new VoidStat(sumOfAreas,numberOfVoids));
		cvClearMemStorage(memory);
	}
	
	public int getBlocksize(IplImage croppedImage, int divider) {
		int blockSize = (croppedImage.width()>croppedImage.height()) ? 
				(((croppedImage.height()/divider)%2==0) ?
						croppedImage.height()/divider+1 : croppedImage.height()/divider)
				: (((croppedImage.width()/divider)%2==0) ? 
						croppedImage.width()/divider+1 : croppedImage.width()/divider);
		if (blockSize <= 1) return 3; else return blockSize;
	}
	
	public void adjustLocation(CvSeq ptr, int up, int left){
		for(int i = 0; i < ptr.total(); i++){
            CvPoint v=new CvPoint(cvGetSeqElem(ptr, i));
            v.put(v.x()+up, v.y()+left);
		}
	}
	
	public boolean isBackground(CvSeq ptr, int width, int height){
		for(int i = 0; i < ptr.total(); i++){
            CvPoint v=new CvPoint(cvGetSeqElem(ptr, i));
            if (v.x()==1||v.y()==1||v.x()==width-2||v.y()==height-2) {
            	return true;
            }
		}
		return false;
	}
	
	public double compactness(double area, double length){
	//	System.out.println(4*Math.PI*area/Math.pow(length, 2));
		return 4*Math.PI*area/Math.pow(length, 2);
	}
	
	public void getEachBall() {
		pointsNum = new int[circlesAL.size()][4];
		for (int j = 0; j < circlesAL.size(); j++) {
			int up = result.height(), down = 0, right = 0, left = result.width();
			
			for (int i = 0; i < circlesAL.get(j).total(); i++) {
				CvPoint v = new CvPoint(cvGetSeqElem(circlesAL.get(j), i));
				if (v.x() > right)
					right = v.x();
				else if (v.x() < left)
					left = v.x();

				if (v.y() > down)
					down = v.y();
				else if (v.y() < up)
					up = v.y();
			}
			pointsNum[j][0] = up;
			pointsNum[j][1] = down;
			pointsNum[j][2] = left;
			pointsNum[j][3] = right;

		}
	}

	public void cropImage() {
		croppedImages = new IplImage[circlesAL.size()];
		for (int j = 0; j < circlesAL.size(); j++) {
			croppedImages[j] = 
					cvCreateImage(new CvSize(pointsNum[j][3]- pointsNum[j][2]
											, pointsNum[j][1] - pointsNum[j][0]),
								 result.depth(), 3);
			cvSetImageROI(
					result,
					cvRect(pointsNum[j][2], 
						   pointsNum[j][0], 
						   pointsNum[j][3]- pointsNum[j][2],
						   pointsNum[j][1]- pointsNum[j][0]));
			cvCopy(result, croppedImages[j]);
			cvResetImageROI(result);
		}
	}

	public void preprocessing() {
		this.getEachBall();
		this.cropImage();
	}
}
